﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Windows.Forms;
using System.Runtime.InteropServices;
using System.ComponentModel;
using Noris.Schedule.Support;

namespace Noris.Tools.FrxEditor.ComponentsLayer
{
    #region LayeredPanel : Třída určená ke kreslení grafiky na plochu. Varianta pro složitější interaktivní motivy.
    /// <summary>
    /// Třída určená ke kreslení grafiky na plochu - absolutně bez blikání.
    /// Varianta pro složitější interaktivní motivy: nabízí více vrstev pro kreslení.
    /// </summary>
    public class LayeredPanel : Panel, IDisposable
    {
        #region Konstrukce, proměnné, vnitřní eventy
        /// <summary>
        /// Třída určená ke kreslení grafiky na plochu - absolutně bez blikání.
        /// Varianta pro složitější interaktivní motivy: nabízí více vrstev pro kreslení.
        /// Konstruktor.
        /// </summary>
        public LayeredPanel()
        {
            this._LayerList = new List<GraphicLayer>();
            this.LayerCount = 1;
            // this.Resize += new EventHandler(_Resize);
            this.Paint += new PaintEventHandler(_Paint);
            this.SetStyle(ControlStyles.AllPaintingInWmPaint |
                ControlStyles.UserPaint |
                ControlStyles.SupportsTransparentBackColor |
                ControlStyles.OptimizedDoubleBuffer, true);
            this.ResizeRedraw = true;
        }
        /// <summary>
        /// Akce při změně velikosti prvku
        /// </summary>
        /// <param name="e"></param>
        protected override void OnSizeChanged(EventArgs e)
        {
            Steward.Diagnostic.InfoAddLineWithTime("LayeredPanel.OnSizeChanged(Size=%0)", this.Size); 
            base.OnSizeChanged(e);            // Zde se změní velikost
            this._ResizeLayers();
        }
        /// <summary>
        /// Nativní událost okna při změně velikosti.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void _Resize(object sender, EventArgs e)
        {
            this._ResizeLayers();
        }
        /// <summary>
        /// Nativní událost okna, když chce překreslit svůj obsah.
        /// Obsah se vezme z bufferovaných vrstev a vykreslí do předané grafiky.
        /// Není k tomu vyvolaná aplikační logika.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void _Paint(object sender, PaintEventArgs e)
        {
            if (this._ReDrawNeed)
                this.ReDraw();
            this._RenderValidLayerTo(e.Graphics);
        }
        /// <summary>
        /// Zajistí kompletní překreslení controlu.
        /// </summary>
        public override void Refresh()
        {
            Steward.Diagnostic.InfoAddLineWithTime("LayeredPanel.Refresh()");
            this.ReDraw();
            base.Refresh();
        }
        #endregion
        #region Public property a metody a eventy
        /// <summary>
        /// Počet vrstev, v rozmezí 1 - 10.
        /// Změna hodnoty vyvolá ReDraw().
        /// </summary>
        public int LayerCount
        {
            get { return this._LayerCount; }
            set
            {
                int cnt = value;
                if (cnt < 1) cnt = 1;
                if (cnt > 10) cnt = 10;
                if (cnt == this._LayerCount) return;
                this._LayerCount = cnt;
                this._CreateLayers();
                this.ReDraw();
            }
        }
        /// <summary>
        /// Preventivní podpora pro budoucí rozšíření o Borders.
        /// ClientArea obsahuje vnitřní prostor controlu zmenšený o Borders.
        /// </summary>
        public Rectangle ClientArea
        {
            get
            {
                int bw = this.BorderWidth;
                return new Rectangle(bw, bw, this.Width - 2 * bw, this.Height - 2 * bw);
            }
        }
        /// <summary>
        /// Šířka borderu
        /// </summary>
        [Category("Appearance")]
        [Description("Border width. Setting value is relevant only when BorderStyle = FixedSingle")]
        public int BorderWidth
        {
            get
            {
                switch (this.BorderStyle)
                {
                    case System.Windows.Forms.BorderStyle.None: return 0;
                    case System.Windows.Forms.BorderStyle.FixedSingle: return this._BorderWidth;
                    case System.Windows.Forms.BorderStyle.Fixed3D: return 2;
                }
                return 0;
            }
            set
            {
                if (this.BorderStyle == System.Windows.Forms.BorderStyle.FixedSingle)
                {
                    int bw = value;
                    if (bw < 0) bw = 0;
                    if (bw > 10) bw = 10;
                    if (bw != this._BorderWidth)
                    {
                        this._BorderWidth = bw;
                        this.ReDraw();
                    }
                }
            }
        }
        private int _BorderWidth = 1;
        /// <summary>
        /// Tato metoda se volá tehdy, když aplikace chce překreslit celý objekt.
        /// </summary>
        public void ReDraw()
        {
            this._ReDraw(null, null);
        }
        /// <summary>
        /// Tato metoda se volá tehdy, když aplikace chce překreslit celý objekt.
        /// </summary>
        public void ReDraw(params int[] layers)
        {
            this._ReDraw(null, layers);
        }
        /// <summary>
        /// Tato metoda se volá tehdy, když aplikace chce překreslit celý objekt.
        /// </summary>
        public void ReDraw(object userData, IEnumerable<int> drawLayers)
        {
            this._ReDraw(userData, drawLayers);
        }
        /// <summary>
        /// Událost, kdy se má překreslit obsah vrstev controlu.
        /// Pokud někdo chce použít tuto třídu bez jejího dědění, pak může svoje kreslení provést v eventhandleru této události.
        /// </summary>
        [Description("Událost, kdy control vyžaduje překreslení určitých vrstev svého obsahu.")]
        [Category("Appearance")]
        public event LayeredPaintEventHandler PaintLayers;
        #endregion
        #region Řízení kreslení: private _ReDraw(); protected OnPaintLayers(); 
        /// <summary>
        /// Zajistí překreslení daných vrstev objektu
        /// </summary>
        /// <param name="userData"></param>
        /// <param name="drawLayers"></param>
        private void _ReDraw(object userData, IEnumerable<int> drawLayers)
        {
            if (this.Width > 0 && this.Height > 0 && this.Parent != null)
            {
                long tick1 = this.StopWatch.ElapsedTicks;
                Steward.Diagnostic.InfoAddLineWithTime("LayeredPanel.ReDraw()");
                LayeredPaintEventArgs e = new LayeredPaintEventArgs(this._LayerCount, this._GetGraphicsForLayer, this._CopyContentOfLayer, drawLayers, userData);
                this.OnPaintLayers(e);
                this._ValidLayer = e.ValidLayer;
                this._ReDrawNeed = false;
                this._RenderValidLayerTo(this.CreateGraphics());
                base.Refresh();
                long tick2 = this.StopWatch.ElapsedTicks;
                this.AddDrawTime(tick2 - tick1);
            }
            else
            {
                this._ReDrawNeed = true;
            }
        }
        /// <summary>
        /// Příznak, že poslední běh metody this._ReDraw() byl vynechán
        /// </summary>
        private bool _ReDrawNeed;
        /// <summary>
        /// Číslo Layeru, který obsahuje validní informaci, která se bude renderovat do controlu.
        /// Určuje ji argument LayeredPaintEventArgs spolu s metodami OnPaintLayers().
        /// </summary>
        private int _ValidLayer;
        /// <summary>
        /// Háček, který se volá při potřebě překreslit obsah objektu.
        /// Typicky jej overriduje potomek a provádí kreslení.
        /// Bázová metoda provádí pouze Clear() vrstvy 0, a to jen tehdy pokud se má kreslit. Clear() vloží do vrstvy 0 barvu this.BackColor.
        /// Dále volá event PaintLayers.
        /// </summary>
        /// <param name="e"></param>
        protected virtual void OnPaintLayers(LayeredPaintEventArgs e)
        {
            // Clear layer 0:
            if (e.HasPaintLayer(0))
            {
                Graphics graphics = e.GraphicsForLayer(0);
                graphics.ResetClip();
                graphics.Clear(this.BackColor);
            }
            // event:
            if (this.PaintLayers != null)
                this.PaintLayers(this, e);
        }
        /// <summary>
        /// Vrátí objekt Graphics pro danou vrstvu layer.
        /// </summary>
        /// <param name="layer"></param>
        /// <returns></returns>
        private Graphics _GetGraphicsForLayer(int layer)
        {
            if (this._LayerList != null && layer >= 0 && layer < this._LayerList.Count)
                return this._LayerList[layer].LayerGraphics;
            return null;
        }
        /// <summary>
        /// Kopíruje obsah vrstvy (layerFrom) to vrstvy (layerTo)
        /// </summary>
        /// <param name="layerFrom"></param>
        /// <param name="layerTo"></param>
        private void _CopyContentOfLayer(int layerFrom, int layerTo)
        {
            GraphicLayer layerSource = this._LayerList[layerFrom];
            Graphics graphicsTarget = this._LayerList[layerTo].LayerGraphics;
            layerSource.RenderTo(graphicsTarget);
        }
        /// <summary>
        /// Fyzicky překreslí obsah bufferů do předané grafiky.
        /// </summary>
        /// <param name="graphics"></param>
        private void _RenderValidLayerTo(Graphics graphics)
        {
            int validLayer = this._ValidLayer;
            if (validLayer >= 0 && validLayer < this._LayerCount)
                this._LayerList[validLayer].RenderTo(graphics);
        }
        /// <summary>
        /// Diagnostické stopky, neměly by se zastavovat
        /// </summary>
        protected System.Diagnostics.Stopwatch StopWatch
        {
            get
            {
                if (this._StopWatch == null)
                    this._StopWatch = System.Diagnostics.Stopwatch.StartNew();
                if (!this._StopWatch.IsRunning)
                    this._StopWatch.Start();
                return this._StopWatch;
            }
        }
        private System.Diagnostics.Stopwatch _StopWatch;
        #endregion
        #region Řízení vrstev: Create, Resize, Dispose. Nikoliv kreslení.
        private void _CreateLayers()
        {
            Size size = this.Size;
            this._LayerList = new List<GraphicLayer>();
            for (int l = 0; l < this._LayerCount; l++)
                this._LayerList.Add(new GraphicLayer(this, this.Size));
        }
        private void _ResizeLayers()
        {
            this._CreateLayers();
            this.ReDraw();
        }
        void IDisposable.Dispose()
        {
            if (this._LayerList != null)
            {
                foreach (GraphicLayer layer in this._LayerList)
                    ((IDisposable)layer).Dispose();
            }
            this._LayerList = null;
            this._LayerCount = 0;
        }
        private int _LayerCount;
        private List<GraphicLayer> _LayerList;
        #endregion
        #region FPS info
        /// <summary>
        /// Rychlost kreslení v jednotkách FPS (frame-per-second)
        /// </summary>
        protected float TimeDrawFPS
        {
            get
            {
                List<long> ticks = this.Ticks;
                int tickCnt = ticks.Count;
                if (tickCnt == 0) return 0f;
                decimal tickSum = (decimal)ticks.Sum();
                decimal tickSec = (decimal)System.Diagnostics.Stopwatch.Frequency;
                // fps = 1 / time1[sec]
                // time1[sec] = (tickSum / tickCnt) / tickSec
                // fps = 1 / ((tickSum / tickCnt) / tickSec) = tickSec / (tickSum / tickCnt)
                return (float)((tickSec * (decimal)tickCnt) / tickSum);
            }
        }
        private void AddDrawTime(long tick)
        {
            List<long> ticks = this.Ticks;
            if (ticks.Count > _TICK_MAX)
                ticks.RemoveRange(0, ticks.Count - _TICK_MAX);
            ticks.Add(tick);
        }
        private List<long> Ticks
        {
            get
            {
                if (this._Ticks == null)
                    this._Ticks = new List<long>();
                return this._Ticks;
            }
        }
        private List<long> _Ticks;
        private const int _TICK_MAX = 9;
        #endregion
        #region class GraphicLayer : třída představující jednu grafickou vrstvu
        /// <summary>
        /// GraphicLayer : třída představující jednu grafickou vrstvu
        /// </summary>
        protected class GraphicLayer : IDisposable
        {
            #region Konstrukce a proměnné
            /// <summary>
            /// Konstruktor vrstvy. Potřebuje referenci na objekt, který jej bude vlastnit, kvůli získávání jeho Graphics.
            /// </summary>
            /// <param name="owner"></param>
            public GraphicLayer(Control owner)
            {
                this._Owner = owner;
            }
            /// <summary>
            /// Konstruktor vrstvy. Potřebuje referenci na objekt, který jej bude vlastnit, kvůli získávání jeho Graphics.
            /// </summary>
            /// <param name="owner"></param>
            /// <param name="size"></param>
            public GraphicLayer(Control owner, Size size)
            {
                this._Owner = owner;
                this.Size = size;
            }
            private Control _Owner;
            /// <summary>
            /// Řídící objekt bufferované grafiky
            /// </summary>
            private BufferedGraphicsContext _BuffGraphContext;
            /// <summary>
            /// Obsah bufferované grafiky, pro rychlejší překreslování a udržení obrazu v paměti i mimo plochu Controlu
            /// </summary>
            private BufferedGraphics _BuffGraphicsData;
            /// <summary>
            /// Velikost
            /// </summary>
            private Size _Size;
            #endregion
            #region Public property a metody
            /// <summary>
            /// true pokud je objekt platný
            /// </summary>
            public bool IsValid
            {
                get { return (!this._Size.IsEmpty && this._BuffGraphContext != null && this._BuffGraphicsData != null); }
            }
            /// <summary>
            /// Rozměr vrstvy.
            /// Lze nastavit, vrstva se upraví. Poté je třeba překreslit (vrstva sama si nevolá).
            /// </summary>
            public Size Size
            {
                get { return this._Size; }
                set
                {
                    if (this.IsValid && value == this._Size) return;         // Není důvod ke změně
                    this._Size = value;
                    this.CreateLayer();
                }
            }
            /// <summary>
            /// Objekt Graphics, který dovoluje kreslit motivy do této vrstvy
            /// </summary>
            public Graphics LayerGraphics { get { return this._BuffGraphicsData.Graphics; } }
            /// <summary>
            /// Vykreslí svůj obsah do dané cílové Graphics, typicky při kreslení Controlu (skládají se jednotlivé vrstvy).
            /// </summary>
            /// <param name="targetGraphic"></param>
            public void RenderTo(Graphics targetGraphic)
            {
                targetGraphic.CompositingMode = CompositingMode.SourceOver;
                this._BuffGraphicsData.Graphics.CompositingMode = CompositingMode.SourceOver;
                this._BuffGraphicsData.Render(targetGraphic);
            }
            #endregion
            #region Privátní tvorba grafiky, IDisposable
            /// <summary>
            /// Vytvoří grafické objekty pro tuto vrstvu
            /// </summary>
            protected void CreateLayer()
            {
                if (this._BuffGraphContext == null)
                    this._BuffGraphContext = BufferedGraphicsManager.Current;

                Size s = this._Size;
                if (s.Width <= 0) s.Width = 1;
                if (s.Height <= 0) s.Height = 1;

                this._BuffGraphContext.MaximumBuffer = new Size(s.Width + 1, s.Height + 1);

                if (this._BuffGraphicsData != null)
                    this._BuffGraphicsData.Dispose();

                this._BuffGraphicsData = this._BuffGraphContext.Allocate(this._Owner.CreateGraphics(), new Rectangle(new Point(0, 0), s));
            }
            void IDisposable.Dispose()
            {
                if (this._BuffGraphContext != null)
                    this._BuffGraphContext.Dispose();
                this._BuffGraphContext = null;

                if (this._BuffGraphicsData != null)
                    this._BuffGraphicsData.Dispose();
                this._BuffGraphicsData = null;

                this._Owner = null;
            }
            #endregion
        }
        #endregion
    }
    #region Delegát LayeredPaintEventHandler, EventArgs LayeredPaintEventArgs
    /// <summary>
    /// Předpis pro handler události LayeredPaint
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    public delegate void LayeredPaintEventHandler(object sender, LayeredPaintEventArgs e);
    /// <summary>
    /// Data pro metodu OnLayeredPaint() / event LayeredPaint
    /// </summary>
    public class LayeredPaintEventArgs : EventArgs
    {
        /// <summary>
        /// Data pro metodu OnLayeredPaint() / event LayeredPaint.
        /// Konstruktor.
        /// </summary>
        /// <param name="layerCount"></param>
        /// <param name="getGraphics"></param>
        /// <param name="copyContentOfLayer"></param>
        /// <param name="layersToPaint"></param>
        /// <param name="userData"></param>
        public LayeredPaintEventArgs(int layerCount, Func<int, Graphics> getGraphics, Action<int, int> copyContentOfLayer, IEnumerable<int> layersToPaint, object userData)
        {
            this._LayerCount = layerCount;
            this._GetGraphics = getGraphics;
            this._CopyContentOfLayer = copyContentOfLayer;
           
            this._LayersToPaint = new Dictionary<int,object>();
            if (layersToPaint != null)
            {
                foreach (int layer in layersToPaint)
                {
                    if (this.LayerExists(layer) && !this._LayersToPaint.ContainsKey(layer))
                        this._LayersToPaint.Add(layer, null);
                }
            }
            else
            {
                for (int layer = 0; layer < layerCount; layer++)
                    this._LayersToPaint.Add(layer, null);
            }

            this.UserData = userData;
        }
        /// <summary>Počet vrstev, slouží ke kontrole čísla vrstvy</summary>
        private int _LayerCount;
        /// <summary>Funkce, která vrátí Graphics pro danou vrstvu</summary>
        private Func<int, Graphics> _GetGraphics;
        /// <summary>Metoda, která překopíruje obsah vrstvy (1) do vrstvy (2)</summary>
        private Action<int, int> _CopyContentOfLayer;
        /// <summary>Soupis vrstev, které se mají kreslit</summary>
        private Dictionary<int, object> _LayersToPaint;
        /// <summary>
        /// Vrací true, pokud dané číslo vrstvy existuje (a může tedy vrátit Graphics)
        /// </summary>
        /// <param name="layer"></param>
        /// <returns></returns>
        public bool LayerExists(int layer)
        {
            if (layer < 0 || layer >= this._LayerCount) return false;
            return true;
        }
        /// <summary>
        /// Vrátí true, pokud se má kreslit do dané vrstvy.
        /// </summary>
        /// <param name="layer"></param>
        /// <returns></returns>
        public bool HasPaintLayer(int layer)
        {
            if (!this.LayerExists(layer)) return false;
            if (this._LayersToPaint == null) return true;
            return this._LayersToPaint.ContainsKey(layer);
        }
        /// <summary>
        /// Soupis vrstev, které se mají kreslit.
        /// </summary>
        public IEnumerable<int> LayersToPaint { get { return this._LayersToPaint.Keys; } }
        /// <summary>
        /// Vrátí objekt Graphics pro danou vrstvu.
        /// Vrací objekt i pro vrstvy, do nichž se kreslit nemusí.
        /// Pro neexistující vrstvy vrací null.
        /// </summary>
        /// <param name="layer"></param>
        /// <returns></returns>
        public Graphics GraphicsForLayer(int layer)
        {
            this.ValidLayer = layer;
            return this._GetGraphics(layer);
        }
        /// <summary>
        /// Zkopíruje obsah vrstvy (layerFrom) do vrstvy (layerTo).
        /// Používá se při kreslení jen části motivu, kdy se jako podklad přebírá již dříve připravený obsah.
        /// </summary>
        /// <param name="layerFrom"></param>
        /// <param name="layerTo"></param>
        public void CopyContentOfLayer(int layerFrom, int layerTo)
        {
            this._CopyContentOfLayer(layerFrom, layerTo);
            this.ValidLayer = layerTo;
        }
        /// <summary>
        /// Libovolná data, předaná do metody ReDraw.
        /// </summary>
        public object UserData { get; private set; }
        /// <summary>
        /// Index vrstvy, která obsahuje validní data.
        /// Automaticky se zde udržuje index vrstvy, která byla posledním cílem operace CopyContentOfLayer(), anebo která byla naposledy vyzvednuta ke kreslení metodou GraphicsForLayer().
        /// Nicméně aplikace může na konci metody override OnPaintLayers() vložit do argumentu do property ValidLayer libovolnou vrstvu, která se bude používat jako zdroj obrazu pro vykreslení controlu.
        /// </summary>
        public int ValidLayer { get; set; }
    }
    #endregion
    #endregion
}
